package sm;

import java.util.Date;
import java.util.Hashtable;
import java.util.Vector;
import com.sun.spot.util.Queue;
import sm.policy.FreqAdaptPolicy;


abstract class SensingService implements Runnable {
	private static int eventId;
	private static long sampleFreq; 
	private Hashtable requestedDataHash;
	private static byte energyCategory;
	 //concurrency
	private TimedQueue queue = new TimedQueue();
/*
   protected  SensingService(){
	   
   }	
   public abstract SensingService getInstance();*/   
   
   public synchronized int getEventId(){
	   return eventId;
    }
   public synchronized long getSampleFreq(){
	   return sampleFreq;
   }
   public synchronized double getValue(){
	   return 123.123;
   }
   public synchronized Hashtable getValue(long requestId){
	   Long requestIdKey = new Long(requestId);
	   Hashtable hashResult = (Hashtable)(requestedDataHash.get(requestIdKey));
	   if (hashResult == null){ //if is to avoid null pointer exception when hashtable.toString()
		   Hashtable hashForNull = new Hashtable();
		  return hashForNull;
	   } else 
		   return hashResult; 
   }
  public void processRequest(long requestId, long sampleFreq1, long duration){
	   Long requestIdKey2 = new Long(requestId);
	   long currentTime = System.currentTimeMillis();
	   long endTime = currentTime + duration;
	   Hashtable localDataHash = new Hashtable();
	   this.adapt();
	   if ( isValidRequest(requestId, sampleFreq1, duration)){	   
			   for (long time = 0 ; time < endTime; time = System.currentTimeMillis()  ){
				   Date date = new Date();
				   Long dateKey = date.getTime();
				   localDataHash.put(dateKey, new Double(this.getValue()));
				   if (requestedDataHash.contains(requestIdKey2)){
					   requestedDataHash.remove(requestIdKey2);
					   requestedDataHash.put(requestIdKey2, localDataHash);
				   }else {
					   requestedDataHash.put(requestIdKey2, localDataHash);
				   }
				   try {
							Thread.sleep(sampleFreq1);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
			   }
	   } else {
		   System.out.println("sampling frequency not supported or repeated requestId");
	   }
   }
   private void adapt(){
	   this.adaptSampleFreq();
   }
   protected synchronized void adaptSampleFreq(){
	   FreqAdaptPolicy policy = new FreqAdaptPolicy();
	   sampleFreq = policy.checkFreq(energyCategory); 
   }
   public synchronized boolean isValidRequest(long requestId, long sampleFreq1, long duration){
	   if ((!isValidFreq(sampleFreq1)) || (requestedDataHash.containsKey(requestId))){
			return false;
	   }
	   return true;
   }
   public synchronized boolean isValidFreq(long sampleFreq1){
	   return (sampleFreq1 >= sampleFreq);
   }
   public synchronized Hashtable getAllData(){
	   return requestedDataHash;
   }
   @Override
   public void run(){
	 RequestObject requestObject1 = (RequestObject)queue.get();	
	 System.out.println(System.currentTimeMillis() + " temp service in new thread " + requestObject1.getRequestId());
	 this.processRequest(requestObject1.getRequestId(), requestObject1.getSampleFreq(), requestObject1.getDuration());
	 System.out.println(System.currentTimeMillis() + " finished request for temp " + requestObject1.getRequestId());
	 Thread.yield();
   }   
   public synchronized void processRequestThread(long requestId, long sampleFreq1, long duration){	
	Long requestIdLong = new Long(requestId);		
	RequestObject requestObject =  new RequestObject();
	requestObject.setRequestId(requestId);
	requestObject.setSampleFreq(sampleFreq1);
	requestObject.setDuration(duration);
	
	queue.put(requestObject);
	System.out.println(System.currentTimeMillis() + " queue size after put " + 
			requestIdLong.toString()+ "  " + queue.size());
	
	Runnable runnable = TempService.getInstance();
	Thread thread = new Thread(runnable, requestIdLong.toString());
	System.out.println(System.currentTimeMillis() + " starting thread " + thread.getName());
	thread.start();	   
   }
public  class RequestObject{
	long requestIdObject;
	long sampleFreqObject;
	long durationObject;
	
	public RequestObject(){    		
	}
	public long getRequestId(){
		return this.requestIdObject;
	}
	public void setRequestId(long requestId){
		this.requestIdObject = requestId;
	}
	public long getSampleFreq(){
		return this.sampleFreqObject;
	}
	public void setSampleFreq(long sampleFreq){
		this.sampleFreqObject = sampleFreq;
	}
	public long getDuration(){
		return this.durationObject;
	}
	public void setDuration(long duration){
		this.durationObject = duration;
	}
}


public class TimedQueue extends Queue{
	private Vector v = new Vector();
	private boolean stopped = false;
	private long waiting = 1000;
	private long timed = System.currentTimeMillis();
	
	/**
	 * Answer whether the receiver is empty.
	 * 
	 * @return - whether the receiver is empty.
	 */
	public boolean isEmpty() {
		synchronized (v) {
			return v.isEmpty();
		}		
	}
	
	/**
	 * Answer the number of elements in the receiver.
	 * 
	 * @return -- the number of elements in the receiver.
	 */
	public int size() {
		synchronized (v) {
			return v.size();
		}		
	}
	
	/**
	 * Add an element to the receiver.
	 *  
	 * @param o -- the Object to add
	 */
	public void put(Object o) {
		synchronized (v) {
			v.addElement(o);
			v.notify();
		}
	}

	/**
	 * Answer an element from the receiver. If the receiver is empty, block
	 * until either an element becomes available, or for timeout milliseconds,
	 * or the queue is stopped. In the event of timeout, return null.
	 * Note that if a waiting thread is interrupted it will return (probably null).
	 * 
	 * @param timeout -- number of milliseconds to wait
	 * @return -- either the first element from the receiver or null after a timeout or if queue is stopped.
	 */
	public Object get(long timeout) {
		synchronized (v) {
			if (timeout > 0 && v.isEmpty() && !stopped) {
				try {
					v.wait(timeout);
				} catch (InterruptedException e) {
					// it's important to catch this exception because we don't notifyAll in
					// put(), so only one thread is unblocked and the thread must consume the
					// element even if it has been interrupted
				}
			}
			return pop();
		}		
	}

	/**
	 * Answer an element from the receiver. If the receiver is empty, block
	 * (possibly forever) until an element becomes available.
	 * Note that if a waiting thread is interrupted it will return (probably null).
	 * 
	 * @return -- the first element from the receiver, or null if queue is stopped
	 */
	public synchronized Object get() {
		synchronized (v) {
			while (v.isEmpty() && !stopped) {
				try {
					v.wait();
				} catch (InterruptedException e) {
					// it's important to catch this exception because we don't notifyAll in
					// put(), so only one thread is unblocked and the thread must consume the
					// element even if it has been interrupted
					break;
				}
			}
			while (System.currentTimeMillis() < timed){
				
			}    			
			//System.out.println(System.currentTimeMillis()+ " in get after waiting");
			timed = System.currentTimeMillis() + waiting;
			return pop(); 
		}		
	}/* for the timed queue to work i had to avoid calling any other methods on the queue
	after the get. I tried a queue.size() and it did not execute it until the next request
	got a return. so a synch queue does only 1 operation at a time on any method.
	
	/**
	 * Release all waiters
	 */
	public void stop() {
		synchronized (v) {
			stopped = true;
			v.notifyAll();
		}		
	}
	
	/**
	 * Drop the contents of the queue.
	 */
	public void empty() {
		synchronized (v) {
			v.removeAllElements();
		}		
	}

	private Object pop() {
		Object result = null;
		if (!v.isEmpty()) {
			result = v.firstElement();
			v.removeElementAt(0);
		}
		return result;
	}
}
}
